#include "../config.h"

#include <glibmm/i18n.h>

#include <gtk/gtk.h>

#include "log.h"
#include "application.h"
#include "command.h"
#include "mainwindow.h"
#include "channellist.h"

namespace gpyp {

//========================
// ChannelListColumn
const int ChannelListColumn::MIN_WIDTH = 8;

void ChannelListColumn::on_presave() {
  conf::Element::on_presave();
  if (*enable_ && !*expand_) {
    width_ = treeviewcolumn_.get_width();
  }
}

void ChannelListColumn::update_ellipsize() {
  renderer_.property_ellipsize() = (*ellipsize_
                                    ? (*right_
                                       ? Pango::ELLIPSIZE_START
                                       : Pango::ELLIPSIZE_END)
                                    : Pango::ELLIPSIZE_NONE);
  expand_on_changed();
}

void ChannelListColumn::width_on_loaded() {
  if (!*expand_) {
    int width = *width_;
    if (width <= 0) width = MIN_WIDTH;
    treeviewcolumn_.set_fixed_width(width);
  }
}
void ChannelListColumn::right_on_changed() {
  renderer_.property_xalign() = *right_ ? 1 : 0;
  update_ellipsize();
}
void ChannelListColumn::expand_on_changed() {
  treeviewcolumn_.set_expand(*expand_);
  if (*expand_) {
    //treeviewcolumn_.set_fixed_width(MIN_WIDTH);
    //treeviewcolumn_.set_resizable(false);
    //treeviewcolumn_.set_sizing(Gtk::TREE_VIEW_COLUMN_AUTOSIZE);
  } else {
    treeviewcolumn_.set_sizing(Gtk::TREE_VIEW_COLUMN_FIXED);
    treeviewcolumn_.set_fixed_width(*width_);
    treeviewcolumn_.set_resizable(true);
  }
}
void ChannelListColumn::ellipsize_on_changed() {
  update_ellipsize();
}

void ChannelListColumn::column_on_clicked() {
  int           sort_column;
  Gtk::SortType sort_order;
  Glib::RefPtr<Gtk::ListStore> liststore = channellist_.model_selected_->get_liststore();
  liststore->get_sort_column_id(sort_column, sort_order);
  Gtk::TreeModelColumn<Glib::ustring> &modelcolumn = *channellist_.model_selected_->get_modelcolumn(*this);
  if (sort_column == modelcolumn.index()) {
    if (sort_order == (*sortasc_ ? Gtk::SORT_DESCENDING : Gtk::SORT_ASCENDING)) {
      liststore->set_sort_column_id(Gtk::TreeSortable::DEFAULT_SORT_COLUMN_ID, Gtk::SORT_ASCENDING);
      return;
    }
    sort_order = sort_order != Gtk::SORT_ASCENDING ? Gtk::SORT_ASCENDING : Gtk::SORT_DESCENDING;
  } else {
    sort_column = modelcolumn.index();
    sort_order = *sortasc_ ? Gtk::SORT_ASCENDING : Gtk::SORT_DESCENDING;
  }
  liststore->set_sort_column(sort_column, sort_order);
}

Glib::ustring ChannelListColumn::format(RefChannel channel) {
  Glib::ustring str;
  format_->format(str, channel, false);
  return str;
}

int ChannelListColumn::compare(Gtk::TreeIter iter1,
                               Gtk::TreeIter iter2,
                               bool          ascending) {
  RefChannel ch1 = channellist_.model_selected_->get_channel(iter1);
  RefChannel ch2 = channellist_.model_selected_->get_channel(iter2);
  ChSort     cs1 = ch1->get_favorite_match()->sort();
  ChSort     cs2 = ch2->get_favorite_match()->sort();
  if (cs1 != cs2) {
    int res = cs1 - cs2;
    return ascending ? res : -res;
  }
  return format_->compare(ch1, ch2);
}

ChannelListColumn::ChannelListColumn(const Glib::ustring &name,
                                     Application         &app,
                                     ChannelList         &channellist,
                                     SlotCellData         slot_celldata) :
  conf::Element (name),
  app_          (app),
  channellist_  (channellist),
  enable_       ("enable",      true),
  title_        ("title",       ""),
  format_       ("format",      "", app_),
  width_        ("width",       100),
  right_        ("right",       false),
  sortasc_      ("sortasc",     true),
  expand_       ("expand",      false),
  ellipsize_    ("ellipsize",   false)
{
  enable_->attach(treeviewcolumn_.property_visible());
  title_ ->attach(treeviewcolumn_.property_title());
  width_->signal_loaded()
    .connect(sigc::mem_fun(*this, &ChannelListColumn::width_on_loaded));
  right_->signal_changed()
    .connect(sigc::mem_fun(*this, &ChannelListColumn::right_on_changed));
  expand_->signal_changed()
    .connect(sigc::mem_fun(*this, &ChannelListColumn::expand_on_changed));
  ellipsize_->signal_changed()
    .connect(sigc::mem_fun(*this, &ChannelListColumn::ellipsize_on_changed));
  right_on_changed();
  expand_on_changed();
  ellipsize_on_changed();

  add(enable_);
  add(title_);
  add(format_);
  add(width_);
  add(right_);
  add(sortasc_);
  add(expand_);
  add(ellipsize_);

  renderer_.property_xpad() = 0;
  renderer_.property_ypad() = 0;

  treeviewcolumn_.set_min_width(MIN_WIDTH);
  treeviewcolumn_.set_resizable(true);
  treeviewcolumn_.set_reorderable(true);

  treeviewcolumn_.pack_start(renderer_);
  treeviewcolumn_.set_cell_data_func(renderer_, slot_celldata);
  treeviewcolumn_.set_clickable(true);
  treeviewcolumn_.signal_clicked().connect(sigc::mem_fun(*this, &ChannelListColumn::column_on_clicked));
}

//========================
// ModelManager
void ModelManager::conf_on_loaded() {
  for(ChannelList::iterator iter = cl_.begin(); iter != cl_.end(); ++iter) {
    column_map_.insert(std::make_pair(*iter, ColumnData::create(*this, *iter)));
  }
  cl_.signal_item_added().connect(sigc::mem_fun(*this, &ModelManager::cl_on_column_added));
  recreate_model_full();
}
void ModelManager::recreate_columns() {
  column_ch_.reset(new ChColumn());
  columns_.reset(new Gtk::TreeModelColumnRecord());
  columns_->add(*column_ch_);
  column_map_.clear();
  for(ChannelList::iterator iter = cl_.begin(); iter != cl_.end(); ++iter) {
    RefColumnData c = ColumnData::create(*this, *iter);
    column_map_.insert(std::make_pair(*iter, c));
    columns_->add(**c);
  }
}
void ModelManager::recreate_model() {
  liststore_ = Gtk::ListStore::create(*columns_);

  if (!slot_compare_.empty()) {
    liststore_->set_default_sort_func(slot_compare_);
  } else {
    liststore_->set_default_sort_func(sigc::mem_fun(*this, &ModelManager::default_sort_func));
  }
  for(ColumnMap::iterator iter = column_map_.begin(); iter != column_map_.end(); ++iter) {
    liststore_->set_sort_func(**iter->second, iter->second->compare_slot());
  }
  liststore_->set_sort_column(*sort_column_, *sort_asc_ ? Gtk::SORT_ASCENDING : Gtk::SORT_DESCENDING);
  liststore_->signal_sort_column_changed()
    .connect(sigc::mem_fun(*this, &ModelManager::on_sort_column_changed));

  filter_ = Gtk::TreeModelFilter::create(liststore_);
  filter_->set_visible_func(sigc::mem_fun(*this, &ModelManager::visible_func));
}
void ModelManager::recreate_model_full() {
  recreate_columns();
  recreate_model();
  set_attributes();
  signal_model_created_();
}

void ModelManager::cl_on_column_added(RefChannelListColumn column) {
  column_map_.insert(std::make_pair(column, ColumnData::create(*this, column)));
  recreate_model_full();
}

int ModelManager::default_sort_func(Gtk::TreeIter iter1, Gtk::TreeIter iter2) {
  RefChannel ch1 = iter1->get_value(*column_ch_);
  RefChannel ch2 = iter2->get_value(*column_ch_);
  if (ch1 && ch2) {
    /*
    RefYPCollection ypc = app_.ypcollection();
    int i = 0, ypi1, ypi2;
    for(YPCollection::iterator iter = ypc->begin();
        iter != ypc->end(); ++iter) {
      if (iter->get() == &ch1->get_yp()) ypi1 = i;
      if (iter->get() == &ch2->get_yp()) ypi2 = i;
      ++i;
    }
    if (ypi1 != ypi2) {
      return ypi1 - ypi2;
    }
    */
    return ch1->get_rank() - ch2->get_rank();
  }
  return 0;
}

void ModelManager::on_sort_column_changed() {
  int           sort_column;
  Gtk::SortType sort_type;
  if (liststore_->get_sort_column_id(sort_column, sort_type)) {
    sort_column_ = sort_column;
    sort_asc_    = sort_type == Gtk::SORT_ASCENDING;
  } else {
    sort_column_ = Gtk::TreeSortable::DEFAULT_SORT_COLUMN_ID;
  }
  signal_sort_column_changed_();
}

bool ModelManager::visible_func(const Gtk::TreeIter &iter) {
  if (!slot_filter_.empty()) return slot_filter_(get_channel(iter));
  return true;
}

Gtk::TreeIter ModelManager::add(RefChannel ch, bool check) {
  if (check && ch->get_id()) {
    Gtk::TreeNodeChildren children = liststore_->children();
    for(Gtk::TreeIter iter = children.begin();
        iter != children.end(); ) {
      Gtk::TreeIter iter_c = iter++;
      if (get_channel(iter_c)->get_id() == ch->get_id()) {
        liststore_->erase(iter_c);
      }
    }
  }
  Gtk::TreeIter iter = liststore_->append();
  iter->set_value(*column_ch_, ch);
  for(ChannelList::iterator citer = cl_.begin();
      citer != cl_.end(); ++citer) {
    iter->set_value(**column_map_[*citer], (*citer)->format(ch));
  }
  return iter;
}
void ModelManager::recreate() {
  recreate_model();
}

void ModelManager::set_filter_func(const SlotFilter &slot_filter) {
  slot_filter_ = slot_filter;
  refilter();
}
void ModelManager::refilter() {
  if (filter_) filter_->refilter();
}

void ModelManager::resort() {
  if (liststore_) {
    int column = 0;
    Gtk::SortType order = Gtk::SORT_ASCENDING;
    liststore_->get_sort_column_id(column, order);
    if (column != Gtk::TreeSortable::DEFAULT_UNSORTED_COLUMN_ID) {
      liststore_->set_sort_column_id(Gtk::TreeSortable::DEFAULT_UNSORTED_COLUMN_ID, order);
      liststore_->set_sort_column_id(column, order);
    }
  }
}

RefChannel ModelManager::get_channel(const Gtk::TreeIter &iter) {
  if (iter) return iter->get_value(*column_ch_);
  return RefChannel();
}
Glib::ustring ModelManager::get_value(const Gtk::TreeIter &iter, RefChannelListColumn column) {
  return iter->get_value(**column_map_[column]);
}
ModelManager::StrColumn *ModelManager::get_modelcolumn(ChannelListColumn &column) {
  for(ColumnMap::iterator iter = column_map_.begin();
      iter != column_map_.end(); ++iter) {
    if (iter->first.get() == &column) {
      return &**iter->second;
    }
  }
  return NULL;
}

bool ModelManager::get_sort_column_id(int &sort_column_id,
                                      Gtk::SortType &order) const {
  return liststore_->get_sort_column_id(sort_column_id, order);
}

void ModelManager::set_attributes() {
  for(ColumnMap::iterator iter = column_map_.begin(); iter != column_map_.end(); ++iter) {
    RefChannelListColumn i = iter->first;
    Gtk::TreeViewColumn &c = i->get_treeviewcolumn();
    c.clear_attributes(i->get_renderer());
    c.add_attribute(i->get_renderer().property_text(), **iter->second);
  }
}

ModelManager::ModelManager(const Glib::ustring &name,
                           Application         &app,
                           ChannelList         &cl,
                           SlotCompare          slot_compare) :
  conf::Element(name),
  app_         (app),
  cl_          (cl),
  sort_column_ ("sort_column", Gtk::TreeSortable::DEFAULT_SORT_COLUMN_ID),
  sort_asc_    ("sort_asc",    true),
  slot_compare_(slot_compare)
{
  conf::Element::add(sort_column_);
  conf::Element::add(sort_asc_);

  app_.conf().signal_loaded().connect(sigc::mem_fun(*this, &ModelManager::conf_on_loaded));
}

//========================
// ChannelList
void ChannelList::treeviewcolumn_set_arrow(Gtk::TreeViewColumn &column, Gtk::SortType sort_type) {
  /* gtkのコードから一部コピペ */
  GtkTreeViewColumn *_column = column.gobj();
  if (!_column->button) {
    return;
  }
  GtkWidget *hbox  = GTK_BIN(_column->button)->child;
  GtkWidget *arrow = _column->arrow;
  gboolean alternative = FALSE;
#if GTK_CHECK_VERSION(2, 12, 0)
  g_object_get(gtk_widget_get_settings(GTK_WIDGET(gobj())),
               "gtk-alternative-sort-arrows", &alternative,
               NULL);
#endif
  if (alternative) {
    sort_type = sort_type == Gtk::SORT_ASCENDING ? Gtk::SORT_DESCENDING : Gtk::SORT_ASCENDING;
  }
  gtk_arrow_set(GTK_ARROW(arrow), sort_type == Gtk::SORT_ASCENDING ? GTK_ARROW_DOWN : GTK_ARROW_UP, GTK_SHADOW_IN);

  g_object_ref(arrow);
  gtk_container_remove(GTK_CONTAINER(hbox), arrow);

  if (_column->xalign <= 0.5) {
    gtk_box_pack_end(GTK_BOX(hbox), arrow, FALSE, FALSE, 0);
  } else {
    gtk_box_pack_start(GTK_BOX(hbox), arrow, FALSE, FALSE, 0);
    gtk_box_reorder_child(GTK_BOX(hbox), arrow, 0);
  }
  g_object_unref(arrow);
  gtk_widget_show(arrow);
}

RefChannelListColumn ChannelList::on_create_item(const Glib::ustring &name) {
  return RefChannelListColumn(new ChannelListColumn(name, app_, *this, sigc::mem_fun(*this, &ChannelList::on_cell_data)));
}
void ChannelList::on_load_default() {
  conf::Collection<ChannelListColumn, ChannelList>::on_load_default();
  add_item(_("Name"), "%{Channel.name}", COLUMNOPT_ELLIPSIZE);
  add_item(_("Description"), "[%{Channel.genre}]%{Channel.desc} - %{Channel.comment} - "
           "%{Channel.track_artist} - %{Channel.track_title} - %{Channel.track_album}",
           COLUMNOPT_EXPAND | COLUMNOPT_ELLIPSIZE);
  add_item(_("Listeners"), "%{Channel.listeners}", COLUMNOPT_RIGHT | COLUMNOPT_SORTDESC);
  add_item(_("Relays"),    "%{Channel.relays}",    COLUMNOPT_RIGHT | COLUMNOPT_SORTDESC);
  add_item(_("Age"),       "%{Channel.age}",       COLUMNOPT_RIGHT);
  add_item(_("Bitrate"),   "%{Channel.bitrate}",   COLUMNOPT_RIGHT | COLUMNOPT_SORTDESC);
}

void ChannelList::prepare_commandopts(CommandOpts &opts) {
  opts.ch = get_selected_channel();
  opts.search = &app_.mainwindow()->filterbar()->searchbar();
}

#ifndef GTK_CELL_LAYOUT_HAVE_GET_CELLS
GtkCellRenderer *ChannelList::get_tooltip_renderer(GtkTreeViewColumn *column) {
  for(iterator iter = begin(); iter != end(); ++iter) {
    if ((*iter)->get_treeviewcolumn().gobj() == column) {
      return GTK_CELL_RENDERER((*iter)->get_renderer().gobj());
    }
  }
  return NULL;
}
#endif

void ChannelList::select_model(RefModelManager model) {
  conn_sort_column_changed_.disconnect();
  conn_model_created_.disconnect();
  model_selected_ = model;
  model_selected_->set_attributes();
  model_on_created();
  conn_sort_column_changed_ = model_selected_->signal_sort_column_changed()
    .connect(sigc::mem_fun(*this, &ChannelList::model_on_sort_column_changed));
  conn_model_created_ = model_selected_->signal_model_created()
    .connect(sigc::mem_fun(*this, &ChannelList::model_on_created));
  refilter();
}
void ChannelList::model_on_created() {
  set_model(model_selected_->get());
  model_on_sort_column_changed();
}

void ChannelList::model_on_changed() {
  switch(*model_) {
  case CLMODEL_PLAYED:
    select_model(model_played_);
    break;
  case CLMODEL_ENDED:
    select_model(model_ended_);
    break;
  case CLMODEL_DEFAULT:
  default:
    select_model(model_default_);
    break;
  }
}

void ChannelList::on_cell_data(Gtk::CellRenderer *cell, const Gtk::TreeIter &iter) {
  if (cell) {
    Gtk::CellRendererText &renderer = *dynamic_cast<Gtk::CellRendererText*>(cell);

    RefChannel       ch = model_selected_->get_channel(iter);
    RefFavoriteMatch fm = ch->get_favorite_match();

    if (*sort_color_ != 0 && sort_column_ && cell == &sort_column_->get_renderer()) {
      if (fm->bgcolor()) {
        renderer.property_cell_background_gdk() = fm->bgcolor().brightness(*sort_color_);
      } else {
        static const Color bgcolor(get_style()->get_base(Gtk::STATE_NORMAL));
        renderer.property_cell_background_gdk() = bgcolor.brightness(*sort_color_);
      }
    } else {
      if (fm->bgcolor()) {
        renderer.property_cell_background_gdk() = fm->bgcolor();
      } else {
        renderer.property_cell_background_set() = false;
      }
    }
    if (fm->fgcolor()) {
      renderer.property_foreground_gdk() = fm->fgcolor();
    } else {
      renderer.property_foreground_set() = false;
    }
    renderer.property_font() = fm->font();
  }
}

namespace {
struct AddEnded : boost::noncopyable {
  RefModelManager        model_;
  RefChannel             selected_ch_;
  const ID              &selected_id_;
  Gtk::TreeRowReference &reselect_row_;
  bool                   reselect_;

  inline void cb(const ID &id, RefChannel ch) {
    Gtk::TreeIter iter = model_->add(ch);
    if (reselect_ &&
        (ch == selected_ch_ ||
         (!reselect_row_ && id == selected_id_))) {
      Glib::RefPtr<Gtk::ListStore> ls = model_->get_liststore();
      reselect_row_ = Gtk::TreeRowReference(ls, ls->get_path(iter));
    }
  }

  inline AddEnded(RefModelManager        model,
                  RefChannel             selected_ch,
                  const ID              &selected_id,
                  Gtk::TreeRowReference &reselect_row,
                  bool                   reselect) :
    model_       (model),
    selected_ch_ (selected_ch),
    selected_id_ (selected_id),
    reselect_row_(reselect_row),
    reselect_    (reselect)
  {
  }
};
}
void ChannelList::ypcollection_on_loaded() {
  bool       reset_model = false;
  RefChannel selected_ch;
  ID         selected_id;
  if (*model_ == CLMODEL_DEFAULT) {
    selected_ch = model_default_->get_channel(get_selection()->get_selected());
    reset_model = true;
  } else if (*model_ == CLMODEL_ENDED) {
    selected_ch = model_ended_->get_channel(get_selection()->get_selected());
    reset_model = true;
  }
  if (selected_ch) selected_id = selected_ch->get_id();

  if (reset_model) {
    hide_tooltip();
    store_scroll_value();
  }

  model_default_->recreate();
  model_ended_  ->recreate();

  Gtk::TreeRowReference reselect_row;
  RefYPCollection ypc = app_.ypcollection();
  bool default_selected = *model_ == CLMODEL_DEFAULT;
  AddEnded func_add_ended(model_ended_,
                          selected_ch,
                          selected_id,
                          reselect_row,
                          *model_ == CLMODEL_ENDED);
  for(YPCollection::iterator ypiter = ypc->begin();
      ypiter != ypc->end(); ++ypiter) {
    RefYellowPage yp = *ypiter;
    if (!*yp->enable()) continue;
    for(YellowPage::iterator chiter = yp->begin();
        chiter != yp->end(); ++chiter) {
      RefChannel ch = *chiter;
      Gtk::TreeIter iter = model_default_->add(ch);
      if (default_selected &&
          (ch == selected_ch ||
           (!reselect_row && ch->get_id() == selected_id))) {
        Glib::RefPtr<Gtk::ListStore> ls = model_default_->get_liststore();
        reselect_row = Gtk::TreeRowReference(ls, ls->get_path(iter));
      }
    }
    yp->get_ended().foreach(sigc::mem_fun(func_add_ended, &AddEnded::cb));
  }

  if (reset_model) {
    unset_model();
    set_model(model_selected_->get());
    model_on_sort_column_changed();

    if (reselect_row) {
      Gtk::TreeIter iter = model_selected_->convert(reselect_row.get_path());
      if (iter) get_selection()->select(iter);
    }
  }
}

bool ChannelList::on_popup(guint button, guint32 activate_time) {
  menu_.popup(button, activate_time);
  return true;
}
bool ChannelList::on_key_press_event(GdkEventKey *event) {
  bool res = WidgetInput<TreeView>::on_key_press_event(event);
  if (!res && search_entry_ && event->length &&
      (event->state & Gtk::AccelGroup::get_default_mod_mask() & ~(Gdk::SHIFT_MASK | Gdk::LOCK_MASK)) == 0) {
    search_parent_->show();
    search_entry_->grab_focus();
    search_entry_->set_text(event->string);
    search_entry_->set_position(-1);
    return true;
  }
  return res;
}
void ChannelList::on_realize() {
  WidgetInput<TreeView>::on_realize();
  model_on_sort_column_changed();
}

void ChannelList::on_add_item(RefChannelListColumn item) {
  conf::Collection<ChannelListColumn, ChannelList>::on_add_item(item);
  connect_queue_draw(item->right());
  connect_queue_draw(item->ellipsize());
  append_column(item->get_treeviewcolumn());
  model_on_sort_column_changed();
}
void ChannelList::on_remove_item(iterator iter, RefChannelListColumn item) {
  remove_column(item->get_treeviewcolumn());
  conf::Collection<ChannelListColumn, ChannelList>::on_remove_item(iter, item);
}
void ChannelList::_on_item_reordered(RefItem column, RefItem prev, RefItem next) {
  conn_columns_changed_.block();
  if (next && *++begin() == next) {
    move_column_to_start(column->get_treeviewcolumn());
  } else {
    move_column_after(column->get_treeviewcolumn(), prev->get_treeviewcolumn());
  }
  conn_columns_changed_.unblock();
}
void ChannelList::_on_columns_changed() {
  Glib::ListHandle<Gtk::TreeViewColumn *> columns = get_columns();
  if (columns.size() < 2) {
    return;
  }
  std::list<iterator> order;
  for(Glib::ListHandle<Gtk::TreeViewColumn *>::iterator citer = columns.begin();
      citer != columns.end(); ++citer) {
    for(iterator iter = begin(); iter != end(); ++iter) {
      if (&(*iter)->get_treeviewcolumn() == *citer) {
        order.push_back(iter);
        break;
      }
    }
  }
  conn_item_reordered_.block();
  reorder_items(order);
  conn_item_reordered_.unblock();
}

void ChannelList::model_on_sort_column_changed() {
  if (!is_realized()) return;
  sort_column_.reset();

  int           sort_column;
  Gtk::SortType sort_order;
  model_selected_->get_sort_column_id(sort_column, sort_order);
  for(iterator iter = begin(); iter != end(); ++iter) {
    Gtk::TreeViewColumn &tvc = (*iter)->get_treeviewcolumn();
    if (model_selected_->get_modelcolumn(**iter)->index() == sort_column) {
      sort_column_ = *iter;
      treeviewcolumn_set_arrow(tvc, sort_order);
    } else {
      GtkWidget *arrow = tvc.gobj()->arrow;
      if (GTK_ARROW(arrow)) {
#if GTK_CHECK_VERSION(2, 10, 0)
        gtk_arrow_set(GTK_ARROW(arrow), GTK_ARROW_NONE, GTK_SHADOW_IN);
#else
        gtk_arrow_set(GTK_ARROW(arrow), (GtkArrowType)0, GTK_SHADOW_IN);
#endif
        gtk_widget_hide(arrow);
      }
    }
  }
}

void ChannelList::on_play(RefChannel ch) {
  if (ch->get_favorite_match()) model_played_->add(ch, true);
}
int ChannelList::played_sort_func(Gtk::TreeIter iter1, Gtk::TreeIter iter2) {
  RefChannel ch1 = model_played_->get_channel(iter1);
  RefChannel ch2 = model_played_->get_channel(iter2);
  if (ch1 && ch2) {
    return (ch2->get_time_last_played() - ch1->get_time_last_played()).total_seconds();
  }
  return 0;
}
int ChannelList::ended_sort_func(Gtk::TreeIter iter1, Gtk::TreeIter iter2) {
  RefChannel ch1 = model_ended_->get_channel(iter1);
  RefChannel ch2 = model_ended_->get_channel(iter2);
  if (ch1 && ch2) {
    bool e1 = ch1->get_is_ended(), e2 = ch2->get_is_ended();
    if (e1 && e2) {
      return (ch2->get_time_ended() - ch1->get_time_ended()).total_seconds();
    } else if (!e1 && !e2) {
      return 0;
    } else if (e1) {
      return -1;
    } else {
      return 1;
    }
  }
  return 0;
}

RefChannelListColumn ChannelList::add_item() {
  return conf::Collection<ChannelListColumn, ChannelList>::add_item();
}
RefChannelListColumn ChannelList::add_item(const Glib::ustring &title,
                                           const Glib::ustring &format,
                                           ColumnOpt opt) {
  RefChannelListColumn column = add_item();
  column->title() = title;
  column->format()->set_value(format);
  if (opt & COLUMNOPT_RIGHT)     column->right()     = true;
  if (opt & COLUMNOPT_SORTDESC)  column->sortasc()   = false;
  if (opt & COLUMNOPT_EXPAND)    column->expand()    = true;
  if (opt & COLUMNOPT_ELLIPSIZE) column->ellipsize() = true;
  return column;
}

RefChannel ChannelList::get_selected_channel() {
  return model_selected_->get_channel(get_selection()->get_selected());
}
void ChannelList::select_iter(Gtk::TreeIter iter) {
  if (iter) {
    get_selection()->select(iter);
    Gtk::TreePath path = model_selected_->get()->get_path(iter);
    if (!path.empty()) set_cursor(path);
  }
}
void ChannelList::select_first() {
  Gtk::TreeNodeChildren children = model_selected_->get()->children();
  if (children.size()) select_iter(children.begin());
}
void ChannelList::select_last() {
  Gtk::TreeNodeChildren children = model_selected_->get()->children();
  if (children.size()) select_iter(--children.end());
}
void ChannelList::select_next() {
  Gtk::TreeIter iter = get_selection()->get_selected();
  if (iter) {
    if (++iter) {
      select_iter(iter);
    }
  } else {
    select_first();
  }
}
void ChannelList::select_prev() {
  Gtk::TreeIter iter = get_selection()->get_selected();
  if (iter) {
    if (iter != model_selected_->get()->children().begin()) {
      select_iter(--iter);
    }
  } else {
    select_last();
  }
}

void ChannelList::set_filter_func(ModelManager::SlotFilter slot_filter) {
  model_default_->set_filter_func(slot_filter);
  model_played_ ->set_filter_func(slot_filter);
  model_ended_  ->set_filter_func(slot_filter);
}

void ChannelList::refilter() {
  hide_tooltip();
  unset_model();
  model_selected_->refilter();
  set_model(model_selected_->get());
}
void ChannelList::resort() {
  hide_tooltip();
  unset_model();
  model_selected_->resort();
  set_model(model_selected_->get());
}

void ChannelList::set_search_widget(Gtk::Widget &parent, Gtk::Entry &entry) {
  search_parent_ = &parent;
  search_entry_  = &entry;
}

void ChannelList::clear_played() {
  bool unset = model_selected_ == model_played_;
  if (unset) unset_model();
  model_played_->recreate();
  if (unset) set_model(model_played_->get());
}
void ChannelList::clear_ended() {
  bool unset = model_selected_ == model_ended_;
  if (unset) unset_model();
  model_ended_->recreate();
  app_.ypcollection()->clear_ended();
  if (unset) set_model(model_ended_->get());
}

bool ChannelList::bc_show_default(ShellLine &/*line*/, CommandOpts &/*opts*/) {
  model_ = CLMODEL_DEFAULT;
  return true;
}
bool ChannelList::bc_show_played(ShellLine &/*line*/, CommandOpts &/*opts*/) {
  model_ = CLMODEL_PLAYED;
  return true;
}
bool ChannelList::bc_show_ended(ShellLine &/*line*/, CommandOpts &/*opts*/) {
  model_ = CLMODEL_ENDED;
  return true;
}
bool ChannelList::bc_toggle_played(ShellLine &/*line*/, CommandOpts &/*opts*/) {
  if (*model_ == CLMODEL_PLAYED) {
    model_ = CLMODEL_DEFAULT;
  } else {
    model_ = CLMODEL_PLAYED;
  }
  return true;
}
bool ChannelList::bc_toggle_ended(ShellLine &/*line*/, CommandOpts &/*opts*/) {
  if (*model_ == CLMODEL_ENDED) {
    model_ = CLMODEL_DEFAULT;
  } else {
    model_ = CLMODEL_ENDED;
  }
  return true;
}
bool ChannelList::bc_clear_played(ShellLine &/*line*/, CommandOpts &/*opts*/) {
  clear_played();
  return true;
}
bool ChannelList::bc_clear_ended(ShellLine &/*line*/, CommandOpts &/*opts*/) {
  clear_ended();
  return true;
}
bool ChannelList::bc_clear(ShellLine &/*line*/, CommandOpts &/*opts*/) {
  switch(*model_) {
  case CLMODEL_PLAYED: clear_played(); break;
  case CLMODEL_ENDED:  clear_ended();  break;
  default: return false;
  }
  return true;
}
bool ChannelList::bc_resort(ShellLine &/*line*/, CommandOpts &/*opts*/) {
  resort();
  return true;
}
bool ChannelList::bc_refilter(ShellLine &/*line*/, CommandOpts &/*opts*/) {
  refilter();
  return true;
}

ChannelList::ChannelList(Application &app, MainWindow &mainwindow) :
  conf::Collection<ChannelListColumn, ChannelList>("channellist", "column"),
  WidgetInput<TreeView>(app, app.conf().input()->channellist(), true),
  app_          (app),
  sort_color_   ("sort_color", -0.1),
  model_        ("model", CLMODEL_DEFAULT),
  model_default_(new ModelManager("model_default", app_, *this)),
  model_played_ (new ModelManager("model_played",  app_, *this,
                                  sigc::mem_fun(*this, &ChannelList::played_sort_func))),
  model_ended_  (new ModelManager("model_ended",   app_, *this,
                                  sigc::mem_fun(*this, &ChannelList::ended_sort_func))),
  search_parent_(NULL),
  search_entry_ (NULL),
  menu_         (app, mainwindow, *this),
  bc_cl_        (*app_.bc().add_ns("Channellist"))
{
  select_model(model_default_);

  app_.ypcollection()->signal_loaded()
    .connect(sigc::mem_fun(*this, &ChannelList::ypcollection_on_loaded));
  app_.commandrunner().signal_play()
    .connect(sigc::mem_fun(*this, &ChannelList::on_play));

  conf::Collection<ChannelListColumn, ChannelList>::add(model_);
  conf::Collection<ChannelListColumn, ChannelList>::add(model_default_);
  conf::Collection<ChannelListColumn, ChannelList>::add(model_played_);
  conf::Collection<ChannelListColumn, ChannelList>::add(model_ended_);

  connect_queue_draw(sort_color_);
  model_->signal_changed()
    .connect(sigc::mem_fun(*this, &ChannelList::model_on_changed));

  conn_item_reordered_ = signal_item_reordered()
    .connect(sigc::mem_fun(*this, &ChannelList::_on_item_reordered));
  conn_columns_changed_ = signal_columns_changed()
    .connect(sigc::mem_fun(*this, &ChannelList::_on_columns_changed));

#define ADD_CMD(CMD, H)                                                 \
  bc_cl_.add_cmd(#CMD, sigc::mem_fun(*this, &ChannelList::bc_##CMD), H)
  ADD_CMD(show_default,  _("Show default channel list"));
  ADD_CMD(show_played,   _("Show played channel list"));
  ADD_CMD(show_ended,    _("Show ended channel list"));
  ADD_CMD(toggle_played, _("Toggle played channel list"));
  ADD_CMD(toggle_ended,  _("Toggle ended channel list"));
  ADD_CMD(clear_played,  _("Clear played channel list"));
  ADD_CMD(clear_ended,   _("Clear ended channel list"));
  ADD_CMD(clear,         _("Clear shown channel list"));
  ADD_CMD(resort,        _("Re-sort channel list"));
  ADD_CMD(refilter,      _("Re-filter channel list"));
#undef ADD_CMD

  set_tooltip_wrap_mode(PANGO_WRAP_CHAR);
  set_headers_clickable(true);
  set_fixed_height_mode(true);
  add_events(Gdk::BUTTON_PRESS_MASK | Gdk::KEY_PRESS_MASK |
             Gdk::LEAVE_NOTIFY_MASK);
}

}/*gpyp*/
